package com.asdhawaii.ics111game.room;

import com.asdhawaii.ics111game.item.Item;
import com.asdhawaii.ics111game.person.Person;
import java.util.ArrayList;
import java.util.List;

/**
 * A defined area in the game world.
 * Room instances do not necessarily correspond to actual rooms;
 * a large room in reality may correspond to several small Room instances,
 * while a single Room instance could encompass an entire floor of a building.
 * 
 * Of all the basic classes (Item, Person, Room), this is the most suited for
 * service as a parent from which to inherit.  Different Rooms are so distinct
 * and offer such a variety of possibilities that limiting them to just the base
 * class would severely restrict the potential of the game.  For example, one may
 * distinguish between Room instances that are inside and outside, or in particular
 * buildings, and so forth; creating separate classes for each of these seems better
 * than trying to fit everything into a single class.
 * 
 * @author Branden Ogata
 *
 */

public class Room
{
  /**
   * The name of this Room.
   * 
   */
  private String name;
  
  /**
   * A short description of this Room.
   * 
   */
  private String shortDescription;
  
  /**
   * A long description of this Room.
   * 
   */
  private String longDescription;
  
  /**
   * The items contained within this Room.
   * 
   */
  private List<Item> contents;
  
  /**
   * The people currently in this Room.
   * 
   */
  private List<Person> inhabitants;
  
  /**
   * The Room to the north of this Room.
   * 
   */
  private Room north;
  
  /**
   * The Room to the northeast of this Room.
   * 
   */
  private Room northeast;
  
  /**
   * The Room to the east of this Room.
   * 
   */
  private Room east;
  
  /**
   * The Room to the southeast of this Room.
   * 
   */
  private Room southeast;
  
  /**
   * The Room to the south of this Room.
   * 
   */
  private Room south;
  
  /**
   * The Room to the southwest of this Room.
   * 
   */
  private Room southwest;
  
  /**
   * The Room to the west of this Room.
   * 
   */
  private Room west;
  
  /**
   * The Room to the northwest of this Room.
   * 
   */
  private Room northwest;
  
  /**
   * The Room above this Room.
   * 
   */
  private Room up;
  
  /**
   * The Room below this Room.
   * 
   */
  private Room down;
  
  /**
   * Initializes a new Room with the given parameters, setting all other fields to default values.
   * 
   * @param name A String containing the name of this new Room.
   * @param shortDescription A String containing the short description of this new Room.
   * @param longDescription A String containing the long description of this new Room.
   * 
   */
  
  public Room(String name, String shortDescription, String longDescription)
  {
    this.name = name;
    this.shortDescription = shortDescription;
    this.longDescription = longDescription;
    this.contents = new ArrayList<Item>();
    this.inhabitants = new ArrayList<Person>();
    this.north = null;
    this.northeast = null;
    this.east = null;
    this.southeast = null;
    this.south = null;
    this.southwest = null;
    this.west = null;
    this.northwest = null;
    this.up = null;
    this.down = null;
  }
  
  /**
   * Returns the name of this Room.
   * 
   * @return this.name The String containing the name of this Room.
   * 
   */
  
  public String getName()
  {
    return this.name;
  }
  
  /**
   * Returns the short description of this Room.
   * Used in response to a LOOK command.
   * This is also automatically called upon entering the room.
   * 
   * @return this.shortDescription The String containing the short description of this Room.
   * 
   */
  
  public String getShortDescription()
  {
    return this.shortDescription;
  }
  
  /** 
   * Returns the long description of this Room.
   * Used in response to an EXAMINE command.
   * 
   * @return this.longDescription The String containing the long description of this Room.
   * 
   */
  
  public String getLongDescription()
  {
    return this.longDescription;
  }
  
  /**
   * Returns the list of Item instances in this Room.
   * 
   * @return items A List of the Item instances in this Room.
   *               This temporary List is used instead of this.contents
   *               to avoid returning a reference to the actual List,
   *               which could cause problems if modified outside of this class.
   * 
   */
  
  public List<Item> getContents()
  {
    List<Item> items = new ArrayList<Item>();
    items.addAll(this.contents);
    return items;
  }
  
  /**
   * Adds an Item to this Room.
   * 
   * @param item The Item to add to this Room.  
   * 
   * @return succeeded A boolean that is true if the addition succeeded,
   *                                     false otherwise.
   *                                   
   */
  
  public boolean addItem(Item item)
  {
    boolean succeeded = false;
    
    // If the Item exists, insert it into the list using binary insert
    if (item != null)
    {
      // Variables for the insert
      int min = 0;
      int max = this.contents.size();
      int median = (min + max) / 2;
      boolean found = false;
      
      // Until a valid position to insert is found 
      // or it becomes impossible to find any other place to insert
      while ((!found) && (min < max)) 
      {
        median = (min + max) / 2;
        
        // If the new Item comes before the median Item alphabetically
        if (item.compareTo(this.contents.get(median)) < 0)
        {
          max = median;
        }
        // If the new Item comes after the median Item alphabetically
        else if (item.compareTo(this.contents.get(median)) > 0)
        {
          min = median + 1;

          // If the search ends on this iteration, 
          // then it is necessary to insert at the position after the current median
          // If the search does not end, then median is overwritten anyway,
          // so the below incrementation has no effect          
          median++;
        }
        // If the new Item is equal to the median Item, then insert here
        // This will probably never happen since Item instances should be unique
        else if (item.compareTo(this.contents.get(median)) == 0)
        {
          found = true;
        }
      }
      
      // If no equal Item was found, then there will not be a duplicate,
      // so it is safe to add the new Item to the contents of this Room
      if (!found)
      {
        this.contents.add(median, item);
        succeeded = true;
      }
    }
    
    return succeeded;
  }
  
  /**
   * Removes and returns an Item from this Room.
   * 
   * @param itemName The String identifying the Item to remove.
   * 
   * @return target The Item that has been removed from this Room.
   *                If no Item with the parameter name was found,
   *                then return null.
   *                
   */
  
  public Item removeItem(String itemName)
  {
    Item target = null;
    
    // If the item name is valid
    if ((itemName != null) && (itemName.length() != 0))
    {
      // Binary search for the Item
      // Variables for the search
      String search = itemName.toLowerCase();
      int min = 0;
      int max = this.contents.size();
      int median = (min + max) / 2;
      boolean found = false;
      
      // Until the Item is found
      // or it becomes impossible to find any more Item instances
      while ((!found) && (min < max)) 
      {
        median = (min + max) / 2;
        
        // If the target Item comes before the median Item alphabetically
        if (search.compareTo(this.contents.get(median).getName().toLowerCase()) < 0)
        {
          max = median;
        }
        // If the target Item comes after the median Item alphabetically
        else if (search.compareTo(this.contents.get(median).getName().toLowerCase()) > 0)
        {
          min = median + 1;
        }
        // If the target Item is equal to the median Item, then get the Item
        // and remove the Item from the contents of this Room
        else if (search.compareTo(this.contents.get(median).getName().toLowerCase()) == 0)
        {
          found = true;
          target = this.contents.get(median);
          this.contents.remove(median);
        }
      }      
    }
    
    return target;
  }
  
  /**
   * Removes all Item instances from this Room.  
   * 
   * @return items The List of Item instances that were in this Room.
   * 
   */
  
  public List<Item> removeAllItems()
  {
    List<Item> items = this.getContents();
    this.contents.clear();
    
    return items;
  }
  
  /**
   * Returns a List of all Person instances in this Room.
   * 
   * @return people The List of Person instances in this Room.
   *                This temporary List is used instead of this.inhabitants
   *                to avoid returning a reference to the actual List,
   *                which could cause problems if modified outside of this class.
   *               
   */
  
  public List<Person> getPeople()
  {
    List<Person> people = new ArrayList<Person>();
    people.addAll(this.inhabitants);
    
    return people;
  }
  
  /**
   * Adds a Person instance to this Room.
   * 
   * @param person The new Person entering this Room.
   * 
   * @return succeeded A boolean that is true if the addition succeeded, 
   *                                      false otherwise.
   *                                      
   */
  
  public boolean addPerson(Person person)
  {
    boolean succeeded = false;

    // If the Item exists, insert it into the list using binary insert
    if (person != null)
    {
      // Variables for the insert
      int min = 0;
      int max = this.inhabitants.size();
      int median = (min + max) / 2;
      boolean found = false;
      
      // Until a valid position to insert is found 
      // or it becomes impossible to find any other place to insert
      while ((!found) && (min < max)) 
      {
        median = (min + max) / 2;
        
        // If the new Item comes before the median Item alphabetically
        if (person.compareTo(this.inhabitants.get(median)) < 0)
        {
          max = median;
        }
        // If the new Item comes after the median Item alphabetically
        else if (person.compareTo(this.inhabitants.get(median)) > 0)
        {
          min = median + 1;
          
          // If the search ends on this iteration, 
          // then it is necessary to insert at the position after the current median
          // If the search does not end, then median is overwritten anyway,
          // so the below incrementation has no effect
          median++;
        }
        // If the new Item is equal to the median Item, then insert here
        // This will probably never happen since Item instances should be unique
        else if (person.compareTo(this.inhabitants.get(median)) == 0)
        {
          found = true;
        }
      }
      
      // If no equal Item was found, then there will not be a duplicate,
      // so it is safe to add the new Item to the contents of this Room
      if (!found)
      {
        this.inhabitants.add(median, person);
        succeeded = true;
      }
    }    
    return succeeded;
  }
  
  /**
   * Removes and returns a Person from this Room.
   * 
   * @param personName The String identifying the Person to remove.
   * 
   * @return target The Person who has been removed from this Room.
   *                If no Person with the parameter name was found,
   *                then return null.
   *                
   */
  
  public Person removePerson(String personName)
  {
    Person target = null;
    
    // If the name of the person is valid
    if ((personName != null) && (personName.length() != 0))
    {
      // Binary search for the Person
      // Variables for the search
      String search = personName.toLowerCase();
      int min = 0;
      int max = this.inhabitants.size();
      int median = (min + max) / 2;
      boolean found = false;
      
      // Until the Item is found
      // or it becomes impossible to find any more Item instances
      while ((!found) && (min < max)) 
      {
        median = (min + max) / 2;
        
        // If the target Item comes before the median Item alphabetically
        if (search.compareTo(this.inhabitants.get(median).getName().toLowerCase()) < 0)
        {
          max = median;
        }
        // If the target Item comes after the median Item alphabetically
        else if (search.compareTo(this.inhabitants.get(median).getName().toLowerCase()) > 0)
        {
          min = median + 1;
        }
        // If the target Item is equal to the median Item, then get the Item
        // and remove the Item from the contents of this Room
        else if (search.compareTo(this.inhabitants.get(median).getName().toLowerCase()) == 0)
        {
          found = true;
          target = this.inhabitants.get(median);
          this.inhabitants.remove(median);
        }
      }      
    }
    
    return target;
  }
  
  /**
   * Removes all Person instances from this Room.  
   * 
   * @return people The List of Person instances that were in this Room.
   * 
   */
  
  public List<Person> removeAllPeople()
  {
    List<Person> people = this.getPeople();
    this.inhabitants.clear();
    
    return people;
  }  

  /**
   * Moves a Person instance from one Room to another Room.
   * 
   * @param personName The String identifying the Person to remove.
   * @param targetRoom The Room to move the Person to.
   * 
   */
  
  public void move(String personName, Room targetRoom)
  {
    // If all parameters exist 
    if ((personName != null) && (personName.length() != 0) && (targetRoom != null))
    {
      // Remove the Person from this Room, then add the Person to targetRoom
      Person temp = this.removePerson(personName);
      
      // Person must have been removed in order to move to another room
      if (temp == null) 
      {
        // Error message here
      }
      else
      {
        targetRoom.addPerson(temp);
      }
    }
  }  
  
  /** 
   * Returns the Room in the designated direction from this Room.
   * Essentially a single access point for the Room getter methods.
   * 
   * @param direction A Direction indicating where to get the Room.
   * 
   * @return destination A Room in the designated direction from this Room.
   *                     Null if there is no exit in the designated direction.
   *                     
   */
  
  public Room getExit(Direction direction)
  {
    Room destination = null;
    
    // Find the appropriate direction, then find the Room in that direction if any
    switch (direction)
    {
      case NORTH:
        destination = this.getNorth();
        break;
      case NORTHEAST:
        destination = this.getNortheast();
        break;
      case EAST:
        destination = this.getEast();
        break;
      case SOUTHEAST:
        destination = this.getSoutheast();
        break;
      case SOUTH:
        destination = this.getSouth();
        break;
      case SOUTHWEST:
        destination = this.getSouthwest();
        break;
      case WEST:
        destination = this.getWest();
        break;
      case NORTHWEST:
        destination = this.getNorthwest();
        break;
      case UP:
        destination = this.getUp();
        break;
      case DOWN:
        destination = this.getDown();
        break;
      default:
        destination = null;
        break;
    }
    
    return destination;
  }
  
  /**
   * Returns the Room to the north of this Room.
   * 
   * @return this.north The Room to the north of this Room.
   * 
   */
  
  public Room getNorth()
  {
    return this.north;
  }

  /**
   * Creates a connection to a Room to the north of this Room.
   * 
   * @param north The Room to place to the north of this Room.
   * 
   */
  
  public void setNorth(Room north)
  {
    this.north = north;
  }
  
  /**
   * Returns the Room to the northeast of this Room.
   * 
   * @return this.northeast The Room to the northeast of this Room.
   * 
   */
  
  public Room getNortheast()
  {
    return this.northeast;
  }

  /**
   * Creates a connection to a Room to the northeast of this Room.
   * 
   * @param northeast The Room to place to the northeast of this Room.
   * 
   */
  
  public void setNortheast(Room northeast)
  {
    this.northeast = northeast;
  }

  /**
   * Returns the Room to the east of this Room.
   * 
   * @return this.east The Room to the east of this Room.
   * 
   */
  
  public Room getEast()
  {
    return this.east;
  }

  /**
   * Creates a connection to a Room to the east of this Room.
   * 
   * @param east The Room to place to the east of this Room.
   * 
   */
  
  public void setEast(Room east)
  {
    this.east = east;
  }

  /**
   * Returns the Room to the southeast of this Room.
   * 
   * @return this.southeast The Room to the southeast of this Room.
   * 
   */
  
  public Room getSoutheast()
  {
    return this.southeast;
  }

  /**
   * Creates a connection to a Room to the southeast of this Room.
   * 
   * @param southeast The Room to place to the southeast of this Room.
   * 
   */
  
  public void setSoutheast(Room southeast)
  {
    this.southeast = southeast;
  }

  /**
   * Returns the Room to the south of this Room.
   * 
   * @return this.south The Room to the south of this Room.
   * 
   */
  
  public Room getSouth()
  {
    return this.south;
  }

  /**
   * Creates a connection to a Room to the south of this Room.
   * 
   * @param south The room to place to the south of this Room.
   * 
   */
  
  public void setSouth(Room south)
  {
    this.south = south;
  }

  /**
   * Returns the Room to the southwest of this Room.
   * 
   * @return this.southwest The Room to the southwest of this Room.
   * 
   */
  
  public Room getSouthwest()
  {
    return this.southwest;
  }

  /**
   * Creates a connection to a Room to the southwest of this Room.
   * 
   * @param southwest The Room to place to the southwest of this Room.
   * 
   */
  
  public void setSouthwest(Room southwest)
  {
    this.southwest = southwest;
  }

  /**
   * Returns the Room to the west of this Room.
   * 
   * @return this.west The Room to the west of this Room.
   * 
   */
  
  public Room getWest()
  {
    return this.west;
  }

  /**
   * Creates a connection to a Room to the west of this Room.
   * 
   * @param west The Room to place to the west of this Room.
   * 
   */
  
  public void setWest(Room west)
  {
    this.west = west;
  }

  /**
   * Returns the Room to the northwest of this Room.
   * 
   * @return this.northwest The Room to the northwest of this Room.
   * 
   */
  
  public Room getNorthwest()
  {
    return this.northwest;
  }

  /**
   * Creates a connection to a Room to the northwest of this Room.
   * 
   * @param northwest The Room to place to the northwest of this Room.
   * 
   */
  
  public void setNorthwest(Room northwest)
  {
    this.northwest = northwest;
  }

  /**
   * Returns the Room above this Room.
   * 
   * @return this.up The Room above this Room.
   * 
   */
  
  public Room getUp()
  {
    return this.up;
  }

  /**
   * Creates a connection to a Room above this Room.
   * 
   * @param up The Room to place above this Room.
   * 
   */
  
  public void setUp(Room up)
  {
    this.up = up;
  }

  /**
   * Returns the Room below this Room.
   * 
   * @return this.down The Room below this Room.
   * 
   */
  
  public Room getDown()
  {
    return this.down;
  }

  /**
   * Creates a connection to a Room below this Room.
   * 
   * @param down The Room to place below this Room.
   * 
   */
  
  public void setDown(Room down)
  {
    this.down = down;
  }

  /**
   * Returns a String representation of this Room.
   * 
   * @return A String describing this Room.
   * 
   */
  
  @Override
  public String toString()
  {
    return this.name;
  }
}
